namespace Warehouse

open System
open System.Data
open System.Collections.ObjectModel
open System.Linq
open System.Windows
open FSharp.Windows

type PurchaseOrderDetailsController(repository : DataModel.Repository) = 
    inherit Controller<PurchaseOrderDetailsEvents, PurchaseOrderDetailsModel>()

    override this.InitModel model = 
        model.AllVendorsList <- repository.Vendors |> Seq.sortBy(fun x -> x.CompanyName)
        model.Override <- Nullable()
        model.OverridePercentage <- false
        model.OverrideTotalAmount <- false

        if model.IsNewEntity 
        then 
            model.Date <- DateTime.Now
            model.LineItems <- ObservableCollection() |> ObservableCollection.deleteEntityOnRemove repository

    override this.Dispatcher = Sync << function
        | Save ok -> this.Save ok
        | BulkInput -> this.BulkInput
        | AddLineItem -> this.AddLineItem
        | NewProductForInvalidUPC -> this.NewProductForInvalidUPC
        | AddInvalidUPCToExistingProduct -> this.AddInvalidUPCToExistingProduct

    member this.Save ok model = 
        model.ClearAllErrors()
        model.Validate()

        if model.IsNewEntity && Seq.isEmpty model.LineItems 
        then
            model |> Validation.setError <@ fun m -> m.LineItems @> "There are no line items."

        if model.IsValid 
        then
//            for li in model.LineItems do
//                li.BasePrice <- li.UPC.Product.ShopCost
                
            assert not(model.OverridePercentage && model.OverrideTotalAmount)

            for li in model.LineItems do
                match model.OverridePercentage, model.OverrideTotalAmount, model.Override.AsOption() with
                | true, false, Some percentage -> 
                    li.Commission <- percentage
                | false, true, Some totalAmount -> 
                    li.Commission <- 
                        let totalForBasePrices = (model.LineItems |> Seq.sumBy (fun li -> decimal li.Quantity * li.BasePrice)) 
                        let commission' = model.Commission.GetValueOrDefault 0M
                        let shipping' = model.Shipping.GetValueOrDefault 0M
                        let extendedTotal = totalAmount * (1M + commission'/100M) + shipping'
                        (extendedTotal - totalForBasePrices) * 100M/ totalForBasePrices |> Decimal.round2
                | _ ->
                    if li.Commission = 0M then li.Commission <- li.UPC.Product.VendorCommission model.Vendor.Id

            model.Save repository

            ok()
        
    member this.BulkInput model = 
        Mvc.startDialog(UpcQuantityPasteView(), UpcQuantityPasteController())
        |> Option.iter (fun upcQtyItemsModel ->

            let groupedByUpc = upcQtyItemsModel.Items |> Seq.groupBy fst |> Seq.map (fun(upc, xs) -> upc, xs |> Seq.map snd |> Seq.sum) 

            model |> Validation.clearError <@ fun m -> m.LineItems @>

            for upCode, quantity in groupedByUpc do
                let lineItem : PurchaseOrderLineItemModel = Model.Create()
                match model.LineItems |> Seq.tryFind (fun li -> li.UPCode = upCode) with
                | Some lineItem -> 
                    lineItem.Quantity <- lineItem.Quantity + quantity
                | None -> 
                    lineItem.UPC <- repository.UpcById upCode
                    if lineItem.UPC = null
                    then
                        lineItem.UPC <- DataModel.UPC(Code = upCode)
                        lineItem |> Validation.setError <@ fun m -> m.UPCode @> "UPC doesn't exist in database."
                    
                    lineItem.Quantity <- quantity
                    model.LineItems.Add lineItem 

            model.SelectedLineItem <- model.LineItems.FirstOrDefault()
        )        

    member this.AddLineItem model = 
        let x = Model.Create()
        let mvc = UpcSearchController.Mvc(x, repository)
        if mvc.StartDialog() 
        then 
            let upcEntity = x.SelectedUPC.Entity
            if model.LineItems |> Seq.exists (fun li -> li.UPCode = upcEntity.Code) 
            then
                let message = sprintf "Line item with upc %s already exists." upcEntity.Code
                MessageBox.Error(message, "Duplicated UPC")
            else
                let li : PurchaseOrderLineItemModel = Model.Create()
                li.UPC <- upcEntity
                model.LineItems.Add li
                model.SelectedLineItem <- li

    member this.NewProductForInvalidUPC model =    
        let view = ProductView()
        let controller = ProductController repository
        let product : ProductModel = Model.Create()
        let upc : UpcModel = Model.Create()
        upc.Code <- model.SelectedLineItem.UPCode
        upc.Note <- repository.NewPackage
        product.UPCs <- ObservableCollection [ upc ]
        let mvc = Mvc(product,view, controller)
        let saveChanges = mvc.StartDialog()
        if saveChanges 
        then 
            model.SelectedLineItem.UPC <- upc.Entity
            model.SelectedLineItem |> Validation.clearError <@ fun m -> m.UPCode @>

    member this.AddInvalidUPCToExistingProduct model =
        let searchResult = Model.Create()
        let mvc = ProductSeach.createMvc searchResult repository     
        if mvc.StartDialog()
        then 
            let product = searchResult.SelectedProduct
            let view = ProductView()
            let controller = ProductController repository
            let upc : UpcModel = Model.Create()
            upc.Code <- model.SelectedLineItem.UPCode
            upc.Note <- repository.NewPackage
            product.UPCs.Add upc
            let mvc = Mvc(product, view, controller)
            let saveChanges = mvc.StartDialog()
            if saveChanges 
            then 
                model.SelectedLineItem.UPC <- upc.Entity
                model.SelectedLineItem |> Validation.clearError <@ fun m -> m.UPCode @>
            else
                product.DiscardChanges()
        
